package com.zhangc.zcscm.zookeeper;

import java.io.File;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;

import javax.security.auth.login.Configuration;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.KeeperException.ConnectionLossException;
import org.apache.zookeeper.KeeperException.NoNodeException;
import org.apache.zookeeper.KeeperException.SessionExpiredException;
import org.apache.zookeeper.Op;
import org.apache.zookeeper.OpResult;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zhangc.zcscm.zookeeper.exception.ZkAuthFailedException;
import com.zhangc.zcscm.zookeeper.exception.ZkException;
import com.zhangc.zcscm.zookeeper.exception.ZkInterruptedException;
import com.zhangc.zcscm.zookeeper.exception.ZkNoNodeException;
import com.zhangc.zcscm.zookeeper.exception.ZkNodeExistsException;
import com.zhangc.zcscm.zookeeper.exception.ZkTimeoutException;

public class ZkClient implements Watcher {
    private static final Logger LOG;
    protected static final String JAVA_LOGIN_CONFIG_PARAM = "java.security.auth.login.config";
    protected static final String ZK_SASL_CLIENT = "zookeeper.sasl.client";
    protected static final String ZK_LOGIN_CONTEXT_NAME_KEY = "zookeeper.sasl.clientconfig";
    protected final IZkConnection _connection;
    private final Map<String, Set<IZkChildListener>> _childListener;
    private final ConcurrentHashMap<String, Set<ZkClient.InnerZkDataListener>> _dataListener;
    private final Set<IZkStateListener> _stateListener;
    private KeeperState _currentState;
    private volatile long lastDisconnectedTime;
    private final ZkLock _zkEventLock;
    private boolean _shutdownTriggered;
    private ZkEventThread _eventThread;
    private Thread _zookeeperEventThread;
    private volatile boolean _closed;
    private boolean _isZkSaslEnabled;
    public static final long DEFAULT_OPERATE_TIMEOUT = 30000L;
    public static final long MAX_OPERATE_TIMEOUT = 315360000000L;

    public ZkClient(String serverString) {
        this(serverString, ZkConnection.Credentials.NONE);
    }

    public ZkClient(String zkServers, ZkConnection.Credentials credentials) {
        this((IZkConnection) (new ZkConnection(zkServers, credentials)));
    }

    public ZkClient(String zkServers, int sessionTimeout, ZkConnection.Credentials credentials) {
        this((IZkConnection) (new ZkConnection(zkServers, sessionTimeout, credentials)));
    }

    public ZkClient(String zkServers, int sessionTimeout, ZkConnection.Credentials credentials, int maxReconnectDelay) {
        this((IZkConnection) (new ZkConnection(zkServers, sessionTimeout, credentials, maxReconnectDelay)));
    }

    public ZkClient(IZkConnection zkConnection) {
        this._childListener = new ConcurrentHashMap();
        this._dataListener = new ConcurrentHashMap();
        this._stateListener = new CopyOnWriteArraySet();
        this.lastDisconnectedTime = System.currentTimeMillis();
        this._zkEventLock = new ZkLock();
        if (zkConnection == null) {
            throw new NullPointerException("Zookeeper connection is null!");
        } else {
            this._connection = zkConnection;
            this._isZkSaslEnabled = this.isZkSaslEnabled();
            this.connect(this);
        }
    }

    public List<String> subscribeChildChanges(String path, IZkChildListener listener, long operationRetryTimeoutInMillis) {
        synchronized (this._childListener) {
            Set<IZkChildListener> listeners = (Set) this._childListener.get(path);
            if (listeners == null) {
                listeners = new CopyOnWriteArraySet();
                this._childListener.put(path, listeners);
            }

            ((Set) listeners).add(listener);
        }

        return this.watchForChilds(path, operationRetryTimeoutInMillis);
    }

    public void unsubscribeChildChanges(String path, IZkChildListener childListener) {
        synchronized (this._childListener) {
            Set<IZkChildListener> listeners = (Set) this._childListener.get(path);
            if (listeners != null) {
                listeners.remove(childListener);
            }

        }
    }

    public ZkClient.ReadResult subscribeDataChanges(String path, IZkDataListener listener, long operationRetryTimeoutInMillis) {
        return this.subscribeDataChanges(path, listener, true, operationRetryTimeoutInMillis);
    }

    public ZkClient.ReadResult subscribeDataChanges(String path, IZkDataListener listener, boolean watchIfNoNodeExisted,
            long operationRetryTimeoutInMillis) {
        synchronized (this._dataListener) {
            Set<ZkClient.InnerZkDataListener> listeners = (Set) this._dataListener.get(path);
            if (listeners == null) {
                listeners = new CopyOnWriteArraySet();
                this._dataListener.put(path, listeners);
            }

            ZkClient.InnerZkDataListener innerZkDataListener = this.findInnerZkDataListener((Set) listeners, listener);
            if (innerZkDataListener != null) {
                innerZkDataListener.watchIfNoNodeExisted = watchIfNoNodeExisted;
            } else {
                innerZkDataListener = new ZkClient.InnerZkDataListener();
                innerZkDataListener.watchIfNoNodeExisted = watchIfNoNodeExisted;
                innerZkDataListener.listener = listener;
                ((Set) listeners).add(innerZkDataListener);
            }
        }

        ZkClient.ReadResult result = this.watchForData(path, watchIfNoNodeExisted, operationRetryTimeoutInMillis);
        LOG.debug("Subscribed data changes for " + path);
        return result;
    }

    private ZkClient.InnerZkDataListener findInnerZkDataListener(Set<ZkClient.InnerZkDataListener> listeners, IZkDataListener listener) {
        ZkClient.InnerZkDataListener result = null;
        Iterator i$ = listeners.iterator();

        while (i$.hasNext()) {
            ZkClient.InnerZkDataListener innerZkDataListener = (ZkClient.InnerZkDataListener) i$.next();
            if (innerZkDataListener.listener.equals(listener)) {
                result = innerZkDataListener;
                break;
            }
        }

        return result;
    }

    public void unsubscribeDataChanges(String path, IZkDataListener dataListener) {
        synchronized (this._dataListener) {
            Set<ZkClient.InnerZkDataListener> listeners = (Set) this._dataListener.get(path);
            if (listeners != null) {
                ZkClient.InnerZkDataListener needRemove = this.findInnerZkDataListener(listeners, dataListener);
                if (needRemove != null) {
                    listeners.remove(needRemove);
                }
            }

            if (listeners == null || listeners.isEmpty()) {
                this._dataListener.remove(path);
            }

        }
    }

    public void subscribeStateChanges(IZkStateListener listener) {
        synchronized (this._stateListener) {
            this._stateListener.add(listener);
        }
    }

    public void unsubscribeStateChanges(IZkStateListener stateListener) {
        synchronized (this._stateListener) {
            this._stateListener.remove(stateListener);
        }
    }

    public void unsubscribeAll() {
        synchronized (this._childListener) {
            this._childListener.clear();
        }

        synchronized (this._dataListener) {
            this._dataListener.clear();
        }

        synchronized (this._stateListener) {
            this._stateListener.clear();
        }
    }

    public void createPersistent(String path, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.createPersistent(path, false, operationRetryTimeoutInMillis);
    }

    public void createPersistent(String path, boolean createParents, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.createPersistent(path, createParents, Ids.OPEN_ACL_UNSAFE, operationRetryTimeoutInMillis);
    }

    public void createPersistent(String path, boolean createParents, List<ACL> acl, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        try {
            this.create(path, (byte[]) null, acl, CreateMode.PERSISTENT, operationRetryTimeoutInMillis);
        } catch (ZkNodeExistsException var8) {
            if (!createParents) {
                throw var8;
            }
        } catch (ZkNoNodeException var9) {
            if (!createParents) {
                throw var9;
            }

            String parentDir = path.substring(0, path.lastIndexOf(47));
            this.createPersistent(parentDir, true, acl, operationRetryTimeoutInMillis);
            this.createPersistent(path, true, acl, operationRetryTimeoutInMillis);
        }

    }

    public void setAcl(final String path, final List<ACL> acl, long operationRetryTimeoutInMillis) throws ZkException {
        if (path == null) {
            throw new NullPointerException("Missing value for path");
        } else if (acl != null && acl.size() != 0) {
            if (!this.exists(path, operationRetryTimeoutInMillis)) {
                throw new RuntimeException("trying to set acls on non existing node " + path);
            } else {
                this.retryUntilConnected(new Callable<Void>() {
                    public Void call() throws Exception {
                        Stat stat = new Stat();
                        ZkClient.this._connection.readData(path, stat, false);
                        ZkClient.this._connection.setAcl(path, acl, stat.getAversion());
                        return null;
                    }
                }, operationRetryTimeoutInMillis);
            }
        } else {
            throw new NullPointerException("Missing value for ACL");
        }
    }

    public Entry<List<ACL>, Stat> getAcl(final String path, long operationRetryTimeoutInMillis) throws ZkException {
        if (path == null) {
            throw new NullPointerException("Missing value for path");
        } else if (!this.exists(path, operationRetryTimeoutInMillis)) {
            throw new RuntimeException("trying to get acls on non existing node " + path);
        } else {
            return (Entry) this.retryUntilConnected(new Callable<Entry<List<ACL>, Stat>>() {
                public Entry<List<ACL>, Stat> call() throws Exception {
                    return ZkClient.this._connection.getAcl(path);
                }
            }, operationRetryTimeoutInMillis);
        }
    }

    public void createPersistent(String path, byte[] data, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.create(path, data, CreateMode.PERSISTENT, operationRetryTimeoutInMillis);
    }

    public void createPersistent(String path, byte[] data, List<ACL> acl, long operationRetryTimeoutInMillis) {
        this.create(path, data, acl, CreateMode.PERSISTENT, operationRetryTimeoutInMillis);
    }

    public String createPersistentSequential(String path, byte[] data, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        return this.create(path, data, CreateMode.PERSISTENT_SEQUENTIAL, operationRetryTimeoutInMillis);
    }

    public String createPersistentSequential(String path, byte[] data, List<ACL> acl, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        return this.create(path, data, acl, CreateMode.PERSISTENT_SEQUENTIAL, operationRetryTimeoutInMillis);
    }

    public void createEphemeral(String path, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.create(path, (byte[]) null, CreateMode.EPHEMERAL, operationRetryTimeoutInMillis);
    }

    public void createEphemeral(String path, List<ACL> acl, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.create(path, (byte[]) null, acl, CreateMode.EPHEMERAL, operationRetryTimeoutInMillis);
    }

    public String create(String path, byte[] data, CreateMode mode, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        return this.create(path, data, Ids.OPEN_ACL_UNSAFE, mode, operationRetryTimeoutInMillis);
    }

    public String create(final String path, final byte[] data, final List<ACL> acl, final CreateMode mode,
            long operationRetryTimeoutInMillis) {
        if (path == null) {
            throw new NullPointerException("Missing value for path");
        } else if (acl != null && acl.size() != 0) {
            return (String) this.retryUntilConnected(new Callable<String>() {
                public String call() throws Exception {
                    return ZkClient.this._connection.create(path, data, acl, mode);
                }
            }, operationRetryTimeoutInMillis);
        } else {
            throw new NullPointerException("Missing value for ACL");
        }
    }

    public void createEphemeral(String path, byte[] data, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.create(path, data, CreateMode.EPHEMERAL, operationRetryTimeoutInMillis);
    }

    public void createEphemeral(String path, byte[] data, List<ACL> acl, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        this.create(path, data, acl, CreateMode.EPHEMERAL, operationRetryTimeoutInMillis);
    }

    public String createEphemeralSequential(String path, byte[] data, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        return this.create(path, data, CreateMode.EPHEMERAL_SEQUENTIAL, operationRetryTimeoutInMillis);
    }

    public String createEphemeralSequential(String path, byte[] data, List<ACL> acl, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        return this.create(path, data, acl, CreateMode.EPHEMERAL_SEQUENTIAL, operationRetryTimeoutInMillis);
    }

    public void process(WatchedEvent event) {
        LOG.debug("Received event: " + event);
        this._zookeeperEventThread = Thread.currentThread();
        boolean stateChanged = event.getPath() == null;
        boolean znodeChanged = event.getPath() != null;
        boolean dataChanged = event.getType() == EventType.NodeDataChanged || event.getType() == EventType.NodeDeleted
                || event.getType() == EventType.NodeCreated || event.getType() == EventType.NodeChildrenChanged;
        this.getEventLock().lock();

        try {
            if (this.getShutdownTrigger()) {
                LOG.debug("ignoring event '{" + event.getType() + " | " + event.getPath() + "}' since shutdown " + "triggered");
                return;
            }

            if (stateChanged) {
                this.processStateChanged(event);
            }

            if (dataChanged) {
                this.processDataOrChildChange(event);
            }
        } finally {
            if (stateChanged) {
                this.getEventLock().getStateChangedCondition().signalAll();
                if (event.getState() == KeeperState.Expired) {
                    this.getEventLock().getZNodeEventCondition().signalAll();
                    this.getEventLock().getDataChangedCondition().signalAll();
                    this.fireAllEvents();
                }
            }

            if (znodeChanged) {
                this.getEventLock().getZNodeEventCondition().signalAll();
            }

            if (dataChanged) {
                this.getEventLock().getDataChangedCondition().signalAll();
            }

            this.getEventLock().unlock();
            LOG.debug("Leaving process event");
        }

    }

    private void fireAllEvents() {
        Iterator i$ = this._childListener.entrySet().iterator();

        Entry entry;
        while (i$.hasNext()) {
            entry = (Entry) i$.next();
            this.fireChildChangedEvents((String) entry.getKey(), (Set) entry.getValue());
        }

        i$ = this._dataListener.entrySet().iterator();

        while (i$.hasNext()) {
            entry = (Entry) i$.next();
            this.fireDataChangedEvents((String) entry.getKey(), (Set) entry.getValue());
        }

    }

    public List<String> getChildren(String path, long operationRetryTimeoutInMillis) {
        return this._getChildren(path, this.hasListeners(path), operationRetryTimeoutInMillis);
    }

    public List<String> getChildren(String path, boolean returnNullIfPathNotExists, long operationRetryTimeoutInMillis) {
        List data = null;

        try {
            data = this._getChildren(path, this.hasListeners(path), operationRetryTimeoutInMillis);
        } catch (ZkNoNodeException var7) {
            if (!returnNullIfPathNotExists) {
                throw var7;
            }
        }

        return data;
    }

    protected List<String> _getChildren(final String path, final boolean watch, long operationRetryTimeoutInMillis) {
        return (List) this.retryUntilConnected(new Callable<List<String>>() {
            public List<String> call() throws Exception {
                return ZkClient.this._connection.getChildren(path, watch);
            }
        }, operationRetryTimeoutInMillis);
    }

    public int countChildren(String path, long operationRetryTimeoutInMillis) {
        try {
            return this.getChildren(path, operationRetryTimeoutInMillis).size();
        } catch (ZkNoNodeException var5) {
            return 0;
        }
    }

    protected boolean exists(final String path, final boolean watch, long operationRetryTimeoutInMillis) {
        return (Boolean) this.retryUntilConnected(new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return ZkClient.this._connection.exists(path, watch);
            }
        }, operationRetryTimeoutInMillis);
    }

    public boolean exists(String path, long operationRetryTimeoutInMillis) {
        return this.exists(path, this.hasListeners(path), operationRetryTimeoutInMillis);
    }

    private void processStateChanged(WatchedEvent event) {
        LOG.info("zookeeper state changed (" + event.getState() + ")");
        this.setCurrentState(event.getState());
        if (!this.getShutdownTrigger()) {
            this.fireStateChangedEvent(event.getState());
            if (event.getState() == KeeperState.Expired) {
                try {
                    this.reconnect();
                    this.fireNewSessionEvents();
                } catch (Exception var3) {
                    LOG.info("Unable to re-establish connection. Notifying consumer of the following exception: ", var3);
                    this.fireSessionEstablishmentError(var3);
                }
            }

        }
    }

    private void fireNewSessionEvents() {
        Iterator i$ = this._stateListener.iterator();

        while (i$.hasNext()) {
            final IZkStateListener stateListener = (IZkStateListener) i$.next();
            this._eventThread.send(new ZkEventThread.ZkEvent("New session event sent to " + stateListener) {
                public void run() throws Exception {
                    stateListener.handleNewSession();
                }
            });
        }

    }

    private void fireStateChangedEvent(final KeeperState state) {
        Iterator i$ = this._stateListener.iterator();

        while (i$.hasNext()) {
            final IZkStateListener stateListener = (IZkStateListener) i$.next();
            this._eventThread.send(new ZkEventThread.ZkEvent("State changed to " + state + " sent to " + stateListener) {
                public void run() throws Exception {
                    stateListener.handleStateChanged(state);
                }
            });
        }

    }

    private void fireSessionEstablishmentError(final Throwable error) {
        Iterator i$ = this._stateListener.iterator();

        while (i$.hasNext()) {
            final IZkStateListener stateListener = (IZkStateListener) i$.next();
            this._eventThread.send(new ZkEventThread.ZkEvent("Session establishment error(" + error + ") sent to " + stateListener) {
                public void run() throws Exception {
                    stateListener.handleSessionEstablishmentError(error);
                }
            });
        }

    }

    private boolean hasListeners(String path) {
        Set<ZkClient.InnerZkDataListener> dataListeners = (Set) this._dataListener.get(path);
        if (dataListeners != null && dataListeners.size() > 0) {
            return true;
        } else {
            Set<IZkChildListener> childListeners = (Set) this._childListener.get(path);
            return childListeners != null && childListeners.size() > 0;
        }
    }

    public boolean deleteRecursive(String path, long operationRetryTimeoutInMillis) {
        List children;
        try {
            children = this._getChildren(path, false, operationRetryTimeoutInMillis);
        } catch (ZkNoNodeException var7) {
            return true;
        }

        Iterator i$ = children.iterator();

        String subPath;
        do {
            if (!i$.hasNext()) {
                return this.delete(path, operationRetryTimeoutInMillis);
            }

            subPath = (String) i$.next();
        } while (this.deleteRecursive(path + "/" + subPath, operationRetryTimeoutInMillis));

        return false;
    }

    private void processDataOrChildChange(WatchedEvent event) {
        String path = event.getPath();
        Set listeners;
        if (event.getType() == EventType.NodeChildrenChanged || event.getType() == EventType.NodeCreated
                || event.getType() == EventType.NodeDeleted) {
            listeners = (Set) this._childListener.get(path);
            if (listeners != null && !listeners.isEmpty()) {
                this.fireChildChangedEvents(path, listeners);
            }
        }

        if (event.getType() == EventType.NodeDataChanged || event.getType() == EventType.NodeDeleted
                || event.getType() == EventType.NodeCreated) {
            listeners = (Set) this._dataListener.get(path);
            if (listeners != null && !listeners.isEmpty()) {
                this.fireDataChangedEvents(event.getPath(), listeners);
            }
        }

    }

    private void fireDataChangedEvents(final String path, Set<ZkClient.InnerZkDataListener> listeners) {
        Iterator i$ = listeners.iterator();

        while (i$.hasNext()) {
            final ZkClient.InnerZkDataListener innerListener = (ZkClient.InnerZkDataListener) i$.next();
            final IZkDataListener listener = innerListener.listener;
            this._eventThread.send(new ZkEventThread.ZkEvent("Data of " + path + " changed sent to " + listener) {
                public void run() throws Exception {
                    try {
                        byte[] data = ZkClient.this.readData(path, (Stat) null, true, 315360000000L);
                        listener.handleDataChange(path, data);
                    } catch (ZkNoNodeException var4) {
                        if (innerListener.watchIfNoNodeExisted && ZkClient.this.exists(path, true, 315360000000L)) {
                            try {
                                listener.handleDataChange(path, ZkClient.this.readData(path, (Stat) null, true, 315360000000L));
                                return;
                            } catch (ZkNoNodeException var3) {
                            }
                        }

                        listener.handleDataDeleted(path);
                    }

                }
            });
        }

    }

    private void fireChildChangedEvents(final String path, Set<IZkChildListener> childListeners) {
        try {
            Iterator i$ = childListeners.iterator();

            while (i$.hasNext()) {
                final IZkChildListener listener = (IZkChildListener) i$.next();
                this._eventThread.send(new ZkEventThread.ZkEvent("Children of " + path + " changed sent to " + listener) {
                    public void run() throws Exception {
                        try {
                            List<String> children = ZkClient.this.getChildren(path, 315360000000L);
                            listener.handleChildChange(path, children);
                        } catch (ZkNoNodeException var4) {
                            if (ZkClient.this.exists(path, true, 315360000000L)) {
                                try {
                                    listener.handleChildChange(path, ZkClient.this.getChildren(path, 315360000000L));
                                    return;
                                } catch (ZkNoNodeException var3) {
                                }
                            }

                            listener.handleChildChange(path, (List) null);
                        }

                    }
                });
            }
        } catch (Exception var5) {
            LOG.error("Failed to fire child changed event. Unable to getChildren.  ", var5);
        }

    }

    public boolean waitUntilExists(String path, TimeUnit timeUnit, long time, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException {
        if (time > 315360000000L) {
            time = 315360000000L;
        }

        Date timeout = new Date(System.currentTimeMillis() + timeUnit.toMillis(time));
        LOG.debug("Waiting until znode '" + path + "' becomes available.");
        if (this.exists(path, operationRetryTimeoutInMillis)) {
            return true;
        } else {
            this.acquireEventLock();

            try {
                while (true) {
                    boolean gotSignal;
                    if (!this.exists(path, true, operationRetryTimeoutInMillis)) {
                        gotSignal = this.getEventLock().getZNodeEventCondition().awaitUntil(timeout);
                        if (gotSignal) {
                            continue;
                        }

                        boolean var9 = false;
                        return var9;
                    }

                    gotSignal = true;
                    return gotSignal;
                }
            } catch (InterruptedException var13) {
                throw new ZkInterruptedException(var13);
            } finally {
                this.getEventLock().unlock();
            }
        }
    }

    private boolean isZkSaslEnabled() {
        boolean isSecurityEnabled = false;
        boolean zkSaslEnabled = Boolean.parseBoolean(System.getProperty("zookeeper.sasl.client", "true"));
        String zkLoginContextName = System.getProperty("zookeeper.sasl.clientconfig", "Client");
        if (!zkSaslEnabled) {
            LOG.warn("Client SASL has been explicitly disabled with zookeeper.sasl.client");
            return false;
        } else {
            String loginConfigFile = System.getProperty("java.security.auth.login.config");
            if (loginConfigFile != null && loginConfigFile.length() > 0) {
                LOG.info("JAAS File name: " + loginConfigFile);
                File configFile = new File(loginConfigFile);
                if (!configFile.canRead()) {
                    throw new IllegalArgumentException("File " + loginConfigFile + "cannot be read.");
                }

                try {
                    Configuration loginConf = Configuration.getConfiguration();
                    isSecurityEnabled = loginConf.getAppConfigurationEntry(zkLoginContextName) != null;
                } catch (Exception var7) {
                    throw new ZkException(var7);
                }
            }

            return isSecurityEnabled;
        }
    }

    public void waitUntilConnected() throws ZkInterruptedException {
        this.waitUntilConnected(315360000000L, TimeUnit.MILLISECONDS);
    }

    public boolean waitUntilConnected(long time, TimeUnit timeUnit) throws ZkInterruptedException {
        return this._isZkSaslEnabled ?
                this.waitForKeeperState(KeeperState.SaslAuthenticated, time, timeUnit) :
                this.waitForKeeperState(KeeperState.SyncConnected, time, timeUnit);
    }

    public boolean waitForKeeperState(KeeperState keeperState, long time, TimeUnit timeUnit) throws ZkInterruptedException {
        if (this._zookeeperEventThread != null && Thread.currentThread() == this._zookeeperEventThread) {
            throw new IllegalArgumentException("Must not be done in the zookeeper event thread.");
        } else {
            if (time > 315360000000L) {
                time = 315360000000L;
            }

            Date timeout = new Date(System.currentTimeMillis() + timeUnit.toMillis(time));
            LOG.info("Waiting for keeper state " + keeperState);
            this.acquireEventLock();

            try {
                boolean stillWaiting = true;

                do {
                    boolean var7;
                    if (this._currentState == keeperState) {
                        LOG.debug("State is " + this._currentState);
                        var7 = true;
                        return var7;
                    }

                    if (!stillWaiting) {
                        var7 = false;
                        return var7;
                    }

                    stillWaiting = this.getEventLock().getStateChangedCondition().awaitUntil(timeout);
                } while (this._currentState != KeeperState.AuthFailed || !this._isZkSaslEnabled);

                throw new ZkAuthFailedException("Authentication failure");
            } catch (InterruptedException var11) {
                throw new ZkInterruptedException(var11);
            } finally {
                this.getEventLock().unlock();
            }
        }
    }

    private void acquireEventLock() {
        try {
            this.getEventLock().lockInterruptibly();
        } catch (InterruptedException var2) {
            throw new ZkInterruptedException(var2);
        }
    }

    public <T> T retryUntilConnected(Callable<T> callable, long operationRetryTimeoutInMillis)
            throws ZkInterruptedException, IllegalArgumentException, ZkException, RuntimeException {
        if (this._zookeeperEventThread != null && Thread.currentThread() == this._zookeeperEventThread) {
            throw new IllegalArgumentException("Must not be done in the zookeeper event thread.");
        } else {
            long operationStartTime = System.currentTimeMillis();

            while (!this._closed) {
                try {
                    return callable.call();
                } catch (ConnectionLossException var7) {
                    Thread.yield();
                    this.waitForRetry(operationRetryTimeoutInMillis);
                } catch (SessionExpiredException var8) {
                    Thread.yield();
                    this.waitForRetry(operationRetryTimeoutInMillis);
                } catch (KeeperException var9) {
                    throw ZkException.create(var9);
                } catch (InterruptedException var10) {
                    throw new ZkInterruptedException(var10);
                } catch (Exception var11) {
                    throw ExceptionUtil.convertToRuntimeException(var11);
                }

                if (operationRetryTimeoutInMillis > -1L
                        && System.currentTimeMillis() - operationStartTime >= operationRetryTimeoutInMillis) {
                    throw new ZkTimeoutException(
                            "Operation cannot be retried because of retry timeout (" + operationRetryTimeoutInMillis + " milli seconds)");
                }
            }

            throw new IllegalStateException("ZkClient already closed!");
        }
    }

    private void waitForRetry(long operationRetryTimeoutInMillis) {
        if (operationRetryTimeoutInMillis < 0L) {
            this.waitUntilConnected();
        } else {
            this.waitUntilConnected(operationRetryTimeoutInMillis, TimeUnit.MILLISECONDS);
        }
    }

    public void setCurrentState(KeeperState currentState) {
        this.getEventLock().lock();

        try {
            if (currentState == KeeperState.Disconnected && this.lastDisconnectedTime == -1L) {
                this.lastDisconnectedTime = System.currentTimeMillis();
            }

            if (currentState == KeeperState.SyncConnected || currentState == KeeperState.SaslAuthenticated) {
                this.lastDisconnectedTime = -1L;
            }

            this._currentState = currentState;
        } finally {
            this.getEventLock().unlock();
        }

    }

    public ZkLock getEventLock() {
        return this._zkEventLock;
    }

    public boolean delete(String path, long operationRetryTimeoutInMillis) {
        return this.delete(path, -1, operationRetryTimeoutInMillis);
    }

    public boolean delete(final String path, final int version, long operationRetryTimeoutInMillis) {
        try {
            this.retryUntilConnected(new Callable<Object>() {
                public Object call() throws Exception {
                    ZkClient.this._connection.delete(path, version);
                    return null;
                }
            }, operationRetryTimeoutInMillis);
            return true;
        } catch (ZkNoNodeException var6) {
            return false;
        }
    }

    public ZkClient.ReadResult readData(String path, Stat stat, long operationRetryTimeoutInMillis) {
        ZkClient.ReadResult readResult = new ZkClient.ReadResult();

        try {
            readResult.data = this.readData(path, stat, this.hasListeners(path), operationRetryTimeoutInMillis);
            readResult.existed = true;
        } catch (ZkNoNodeException var7) {
            readResult.data = null;
            readResult.existed = false;
        }

        return readResult;
    }

    public ZkClient.ReadResult readData(String path, long operationRetryTimeoutInMillis) {
        return this.readData(path, (Stat) null, operationRetryTimeoutInMillis);
    }

    protected byte[] readData(final String path, final Stat stat, final boolean watch, long operationRetryTimeoutInMillis) {
        return (byte[]) this.retryUntilConnected(new Callable<byte[]>() {
            public byte[] call() throws Exception {
                return ZkClient.this._connection.readData(path, stat, watch);
            }
        }, operationRetryTimeoutInMillis);
    }

    public void writeData(String path, byte[] object, long operationRetryTimeoutInMillis) {
        this.writeData(path, object, -1, operationRetryTimeoutInMillis);
    }

    public void writeData(String path, byte[] datat, int expectedVersion, long operationRetryTimeoutInMillis) {
        this.writeDataReturnStat(path, datat, expectedVersion, operationRetryTimeoutInMillis);
    }

    public Stat writeDataReturnStat(final String path, final byte[] data, final int expectedVersion, long operationRetryTimeoutInMillis) {
        return (Stat) this.retryUntilConnected(new Callable<Object>() {
            public Object call() throws Exception {
                return ZkClient.this._connection.writeDataReturnStat(path, data, expectedVersion);
            }
        }, operationRetryTimeoutInMillis);
    }

    private ZkClient.ReadResult watchForData(final String path, final boolean watchIfNoNodeExisted, long operationRetryTimeoutInMillis) {
        return (ZkClient.ReadResult) this.retryUntilConnected(new Callable<ZkClient.ReadResult>() {
            public ZkClient.ReadResult call() throws Exception {
                ZkClient.ReadResult readResult = ZkClient.this.new ReadResult();

                try {
                    readResult.data = ZkClient.this._connection.readData(path, (Stat) null, true);
                    readResult.existed = true;
                } catch (NoNodeException var5) {
                    if (watchIfNoNodeExisted && ZkClient.this._connection.exists(path, true)) {
                        try {
                            readResult.data = ZkClient.this._connection.readData(path, (Stat) null, true);
                            readResult.existed = true;
                            return readResult;
                        } catch (NoNodeException var4) {
                        }
                    }

                    readResult.data = null;
                    readResult.existed = false;
                }

                return readResult;
            }
        }, operationRetryTimeoutInMillis);
    }

    public List<String> watchForChilds(final String path, long operationRetryTimeoutInMillis) {
        if (this._zookeeperEventThread != null && Thread.currentThread() == this._zookeeperEventThread) {
            throw new IllegalArgumentException("Must not be done in the zookeeper event thread.");
        } else {
            return (List) this.retryUntilConnected(new Callable<List<String>>() {
                public List<String> call() throws Exception {
                    try {
                        return ZkClient.this._connection.getChildren(path, true);
                    } catch (NoNodeException var4) {
                        if (ZkClient.this._connection.exists(path, true)) {
                            try {
                                return ZkClient.this._connection.getChildren(path, true);
                            } catch (NoNodeException var3) {
                            }
                        }

                        return null;
                    }
                }
            }, operationRetryTimeoutInMillis);
        }
    }

    public void connect(Watcher watcher) throws ZkInterruptedException, ZkTimeoutException, IllegalStateException {
        boolean started = false;
        this.acquireEventLock();

        try {
            this.setShutdownTrigger(false);
            this._eventThread = new ZkEventThread(this._connection.getServers());
            this._eventThread.start();
            this._connection.connect(watcher);
            started = true;
        } finally {
            this.getEventLock().unlock();
            if (!started) {
                this.close();
            }

        }

    }

    public long getCreationTime(String path) {
        this.acquireEventLock();

        long var2;
        try {
            var2 = this._connection.getCreateTime(path);
        } catch (KeeperException var8) {
            throw ZkException.create(var8);
        } catch (InterruptedException var9) {
            throw new ZkInterruptedException(var9);
        } finally {
            this.getEventLock().unlock();
        }

        return var2;
    }

    public void close() throws ZkInterruptedException {
        if (!this._closed) {
            LOG.debug("Closing ZkClient...");
            this.getEventLock().lock();

            try {
                this.setShutdownTrigger(true);
                this._eventThread.interrupt();
                this._eventThread.join(2000L);
                this._connection.close();
                this._closed = true;
            } catch (InterruptedException var5) {
                throw new ZkInterruptedException(var5);
            } finally {
                this.getEventLock().unlock();
            }

            LOG.debug("Closing ZkClient...done");
        }
    }

    private void reconnect() {
        this.getEventLock().lock();

        try {
            this._connection.reconnect(this);
        } catch (InterruptedException var5) {
            throw new ZkInterruptedException(var5);
        } finally {
            this.getEventLock().unlock();
        }

    }

    public void setShutdownTrigger(boolean triggerState) {
        this._shutdownTriggered = triggerState;
    }

    public boolean getShutdownTrigger() {
        return this._shutdownTriggered;
    }

    public int numberOfListeners() {
        int listeners = 0;

        Iterator i$;
        Set dataListeners;
        for (i$ = this._childListener.values().iterator(); i$.hasNext(); listeners += dataListeners.size()) {
            dataListeners = (Set) i$.next();
        }

        for (i$ = this._dataListener.values().iterator(); i$.hasNext(); listeners += dataListeners.size()) {
            dataListeners = (Set) i$.next();
        }

        listeners += this._stateListener.size();
        return listeners;
    }

    public List<OpResult> multi(final Iterable<Op> ops, long operationRetryTimeoutInMillis) throws ZkException {
        if (ops == null) {
            throw new NullPointerException("ops must not be null.");
        } else {
            return (List) this.retryUntilConnected(new Callable<List<OpResult>>() {
                public List<OpResult> call() throws Exception {
                    return ZkClient.this._connection.multi(ops);
                }
            }, operationRetryTimeoutInMillis);
        }
    }

    public boolean isAlive(long maxDisconnectTime) {
        this.getEventLock().lock();

        boolean var3;
        try {
            var3 = this.lastDisconnectedTime == -1L || System.currentTimeMillis() - this.lastDisconnectedTime < maxDisconnectTime;
        } finally {
            this.getEventLock().unlock();
        }

        return var3;
    }

    public boolean isAlive() {
        return this.isAlive(120000L);
    }

    public void addBackgroundJob(String jobName, final Runnable runnable) {
        if (this._eventThread == null) {
            throw new IllegalStateException("Event thread not create yet.");
        } else {
            this._eventThread.send(new ZkEventThread.ZkEvent(jobName) {
                public void run() throws Exception {
                    runnable.run();
                }
            });
        }
    }

    public long getSessionId() {
        return this._connection.getSessionId();
    }

    static {
        //VersionStatistics.reportVersion(ZkClient.class);
        LOG = LoggerFactory.getLogger(ZkClient.class);
    }

    public class ReadResult {
        public boolean existed;
        public byte[] data;

        public ReadResult() {
        }
    }

    private class InnerZkDataListener {
        IZkDataListener listener;
        volatile boolean watchIfNoNodeExisted;

        private InnerZkDataListener() {
        }
    }
}
